package se.webbzon.boltzmann.minigame.sandbox;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Collection;

import se.webbzon.boltzmann.configuration.Graphics;
import se.webbzon.oschi01.worldarea.WorldArea;
import se.webbzon.oschi01.worldcamera.WorldCameraLayer;
import se.webbzon.oschi01.worldcamera.WorldCameraPane;
import se.webbzon.oschi01.worldobject.WorldObject;

public class SandBox {
	
	// The 2-dimensional depth of this sand box.
	private static final int startDepth = 1000, endDepth = 0;
	
	// The maximal distance for dropping items in containers
	private static final double maxDropDistance = 32;
	
	// The offset of the box relative to the center of the heads-up display
	private static final int xOffset = 0, yOffset = -40;
	
	// The world object displaying this sand box
	private final WorldObject bg, fg;
	
	// The layer of the sand box
	private final WorldCameraLayer layer;
	
	// A collection of the sand box items registered for this sand box 
	private final Collection<SandBoxItem> items;
	
	// A collections of all the listeners to this sand box. 
	private final Collection<SandBoxListener> listeners;
	
	/*============================================================
	Constructors
	============================================================*/

	public SandBox(WorldArea world) {
		bg = new Background();	
		fg = new Foreground();
		layer = new WorldCameraLayer(new Dimension(624,264));
		items = new ArrayList<SandBoxItem>();
		listeners = new ArrayList<SandBoxListener>();
		world.createInstance(bg);
		world.createInstance(fg);
	}
	
	/*============================================================
	Public Methods
	============================================================*/
	
	/** Returns the world area of this sand box. **/
	public WorldArea getWorld() {
		return bg.getWorld();
	}
	
	/** Adds a listener to this sand box. **/
	public boolean addListener(SandBoxListener listener) {
		return listeners.add(listener);
	}
	
	/** Removes a listener from this sand box. **/
	public boolean removeListener(SandBoxListener listener) {
		return listeners.remove(listener);
	}
	
	/** Clears all the listeners from this sand box. **/
	public void clearListeners() {
		listeners.clear();
	}
	
	/** Copies the boundary of the sand box to the provided rectangle. **/
	public void getBoundary(Rectangle rect) {
		Dimension d = new Dimension();
		Graphics.instance().getDimension(d);
		Dimension l = new Dimension();
		layer.getDimension(l);
		int x = (d.width - l.width) / 2 + xOffset;
		int y = (d.height - l.height) / 2 + yOffset;
		rect.setBounds(x, y, l.width, l.height);
	}
	
	/** Returns true if the provided sand box item is within the
	 * sand box. **/
	public boolean isWithinSandbox(SandBoxItem item) {
		return isWithinSandbox(item.getX(),item.getY());
	}
	
	/** Returns true if the provided point is within the sand box. **/
	public boolean isWithinSandbox(double x, double y) {
		Rectangle rect = new Rectangle();
		getBoundary(rect);
		return rect.contains(x, y);
	}
	
	/** Returns true if the provided sand box item was added to the sand box. **/
	public boolean addItem(SandBoxItem item) {
		if (items.add(item)) {
			item.onAttach(this);
			return true;
		} else
			return false;
	}
	
	/** Returns true if the provided sand box item was removed from the sand box. **/
	public boolean removeItem(SandBoxItem item) {
		if (items.remove(item)) {
			item.onDetach(this);
			return true;
		} else
			return false;
	}
	
	/** Clears all the sand box items of this sand box. **/
	public void clearContainers() {
		for (SandBoxItem item : items.toArray(new SandBoxItem[0])) {
			item.onDetach(this);
		}
		items.clear();
	}
	
	/** Returns the container in which the provided item was dropped. If
	 * the item could not be dropped then null is returned. **/
	public SandBoxContainer drop(SandBoxItem item) {
		final double xc = item.getX();
		final double yc = item.getY();
		for (SandBoxItem i : items) {
			if (i instanceof SandBoxContainer) {
				SandBoxContainer c = (SandBoxContainer) i;
				if (c.isEmpty()) {
					final double dx = xc - c.getX();
					final double dy = yc - c.getY();
					if (Math.hypot(dx, dy) < maxDropDistance) {
						c.setContent(item);
						notifyDrop(item,c);
						return c;
					}
				}
			}
		}
		notifyDrop(item,null);
		return null;
	}
	
	/** Returns the item that could be picked up from the provided location. 
	 * If no item was located at the position then null is returned. **/
	public SandBoxItem pick(double x, double y) {
		SandBoxItem pickedItem = null;
		double closestDistance = maxDropDistance;
		for (SandBoxItem item : items) {
			if (item.draggable()) {
				double d = Math.hypot(x - item.getX(), y - item.getY());
				if (d < closestDistance) {
					closestDistance = d;
					pickedItem = item;
				}
			}
		}
		
		if (pickedItem == null) {
			notifyInvoke(x,y);
			return null;
		} else for (SandBoxItem item : items) {
			if (item instanceof SandBoxContainer) {
				SandBoxContainer c = (SandBoxContainer) item;
				if (c.getContent() == pickedItem) {
					c.setContent(null);
					notifyPickup(pickedItem,c);
					return pickedItem;
				}
			}
		}
		
		notifyPickup(pickedItem,null);
		return pickedItem;
	}

	/*============================================================
	Private Methods
	============================================================*/
	
	/** Notifies a pickup event for all the listeners. **/
	private final void notifyPickup(SandBoxItem item, SandBoxContainer container) {
		for (SandBoxListener listener : listeners.toArray(new SandBoxListener[0]))
			listener.onPick(item, container);
	}
	
	/** Notifies a drop event for all the listeners. **/
	private final void notifyDrop(SandBoxItem item, SandBoxContainer container) {
		for (SandBoxListener listener : listeners.toArray(new SandBoxListener[0]))
			listener.onDrop(item, container);
	}
	
	/** Notifies an invoke event for all the listeners. **/
	private final void notifyInvoke(double x, double y) {
		for (SandBoxListener listener : listeners.toArray(new SandBoxListener[0]))
			listener.onInvoke(x, y);
	}
	
	/*============================================================
	Private Classes
	============================================================*/
	
	/** The background of the sand box. **/
	private final class Background extends WorldObject {

		public Background() {
			super(WorldObject.onPostDraw, startDepth);
		}
		
		@Override public void onPostDraw(WorldArea world, WorldCameraPane hud) {
			Dimension l = new Dimension();
			layer.clear();
			layer.getDimension(l);
			layer.setColor(Color.lightGray);
			layer.setTransparency(0.9f);
			layer.fillRect(0, 0, l.width, l.height);
			layer.setTransparency(1);
			layer.setColor(Color.blue);
			layer.drawRect(0, 0, l.width, l.height);
			hud.pushLayer(layer);
		}
		
	}
	
	/** The foreground of the sand box. **/
	private final class Foreground extends WorldObject {

		public Foreground() {
			super(WorldObject.onPostDraw, endDepth);
		}
		
		@Override public void onPostDraw(WorldArea world, WorldCameraPane hud) {
			Dimension l = new Dimension(), d = new Dimension();
			layer.getDimension(l);
			//layer.clip(0, 0, 160, 160);
			hud.popLayer();
			hud.getLayer().getDimension(d);
			int x = (d.width - l.width) / 2 + xOffset;
			int y = (d.height - l.height) / 2 + yOffset;
			hud.drawLayer(x, y, layer);
		}
		
	}

}
